热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

篇文章|也就是_重读Spring系列Conditional注入Bean的判断

篇首语:本文由编程笔记#小编为大家整理,主要介绍了重读Spring系列-Conditional注入Bean的判断相关的知识,希望对你有一定的参考价值。这一篇我们要梳理的主

篇首语:本文由编程笔记#小编为大家整理,主要介绍了重读Spring系列-Conditional注入Bean的判断相关的知识,希望对你有一定的参考价值。


这一篇我们要梳理的主要是关于注入Bean的条件判断,只有满足对应的条件才注入对应的BeanDefinition。关于注入的条件判断目前我们知道是有3个地方,其中两个都是使用的ConditionEvaluator处理,另外一个是我们的上一篇提到的AutoConfigurationImportFilter。下面我们就按顺序来依次梳理。建议看下前两篇


一、@Conditional

1、结构

@Target(ElementType.TYPE, ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional
Class<? extends Condition>[] value();

​ 首先我们需要了解这个注解&#xff0c;这个注解就是用来条件判断的&#xff0c;只有满足条件(通过value导入的继承Condition接口的类来判断)&#xff0c;才导入这个Bean。下面我们通过一个spring源码中的demo来看下这个注解的具体配套使用。


2、demo1

&#64;Configuration
&#64;Conditional(NoBeanOneCondition.class)
static class BeanTwoConfiguration
&#64;Bean
public ExampleBean bean2()
return new ExampleBean();


​ 这个配置类BeanTwoConfiguration&#xff0c;我们可以看到其本身是要注入bean2()(ExampleBean)的&#xff0c;但其加了一个&#64;Conditional(NoBeanOneCondition.class)&#xff0c;就是说这个配置类能不能注入&#xff0c;需要看NoBeanOneCondition的判断。

static class NoBeanOneCondition implements Condition
&#64;Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
return !context.getBeanFactory().containsBeanDefinition("bean1");


​ 这个判断就是看当前Bean工厂中有没有名称为bean1BeanDefiniton&#xff0c;如果没有就返回true&#xff0c;这样就会注入BeanTwoConfiguration以此来注入其中的ExampleBean

&#64;Test
public void conditionalOnMissingBeanNoMatch() throws Exception
AnnotationConfigApplicationContext ctx &#61; new AnnotationConfigApplicationContext();
ctx.register(BeanTwoConfiguration.class);
ctx.refresh();
assertFalse(ctx.containsBean("bean1"));
assertTrue(ctx.containsBean("bean2"));
assertTrue(ctx.containsBean("configurationClassWithConditionTests.BeanTwoConfiguration"));

​ 这个就是测试类&#xff0c;可以看到其只ctx.register(BeanTwoConfiguration.class)&#xff0c;即只注入了BeanTwoConfiguration&#xff0c;就还没有bean1&#xff0c;所以这里就会成功注入bean2(ExampleBean)。


3、demo2

在上面的demo1基础上&#xff0c;我们来看下另一种情况&#xff1a;

&#64;Configuration
static class BeanOneConfiguration
&#64;Bean
public ExampleBean bean1()
return new ExampleBean();


​ 这个BeanOneConfiguration就是注解注入bean1&#xff0c;然后看测试类&#xff1a;

&#64;Test
public void conditionalOnMissingBeanMatch() throws Exception
AnnotationConfigApplicationContext ctx &#61; new AnnotationConfigApplicationContext();
ctx.register(BeanOneConfiguration.class, BeanTwoConfiguration.class);
ctx.refresh();
assertTrue(ctx.containsBean("bean1"));
assertFalse(ctx.containsBean("bean2"));
assertFalse(ctx.containsBean("configurationClassWithConditionTests.BeanTwoConfiguration"));

​ 这样就没有注入configurationClassWithConditionTests.BeanTwoConfiguration了&#xff0c;所以也不会注入bean2


二、&#64;Conditional在SpringBoot中的使用

上面这个是简单使用。下面我们就来其在SpringBoot中的使用&#xff0c;这个其实我们上一篇文章有简单提到。下面这些注解都是用在SpringBoot的自动配置。


1、&#64;ConditionalOnBean

&#64;Target( ElementType.TYPE, ElementType.METHOD )
&#64;Retention(RetentionPolicy.RUNTIME)
&#64;Documented
&#64;Conditional(OnBeanCondition.class)
public &#64;interface ConditionalOnBean
Class<?>[] value() default ;
String[] type() default ;
...........

​ 这个注解&#64;ConditionalOnBean有点类似上面demo的NoBeanOneCondition这种&#xff0c;只不过NoBeanOneCondition是类&#xff0c;还要自动注入&#xff0c;这个&#64;ConditionalOnBean只要注解标注就可以了。不过两者的核心都是通过&#64;Conditional来导入Condition接口的实现类。这里是导入的OnBeanCondition

​ 然后这个&#64;ConditionalOnBean只要是用来判断有没有Bean&#xff0c;只有存在的话才注入。下面我们来看下使用demo


1&#xff09;、demo

&#64;Configuration
&#64;ConditionalOnBean(name &#61; "foo")
protected static class OnBeanNameConfiguration
&#64;Bean
public String bar()
return "bar";


&#64;Configuration
protected static class FooConfiguration
&#64;Bean
public String foo()
return "foo";


&#64;Test
public void testNameOnBeanCondition()
this.contextRunner.withUserConfiguration(FooConfiguration.class, OnBeanNameConfiguration.class)
.run(this::hasBarBean);

private void hasBarBean(AssertableApplicationContext context)
assertThat(context).hasBean("bar");
assertThat(context.getBean("bar")).isEqualTo("bar");

​ 这样就完成了OnBeanNameConfiguration的注入&#xff0c;当然这里还可以指定这个Bean的类似&#xff0c;可以通过value、或type&#xff1a;

&#64;Configuration
&#64;ConditionalOnBean(name &#61; "foo", value &#61; Date.class)
protected static class OnBeanNameAndTypeConfiguration
&#64;Bean
public String bar()
return "bar";


&#64;Test
public void testNameAndTypeOnBeanCondition()
this.contextRunner.withUserConfiguration(FooConfiguration.class, OnBeanNameAndTypeConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean("bar"));

​ 这样由于类型不匹配&#xff0c;就不能注入了。

当然SpringBoot还有很多的这种注解&#xff0c;来帮助自动配置与注解。

​ 看名称也大致知道其的作用了。我们下面就来看下OnBeanCondition具体逻辑。


三、OnBeanCondition

class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition

​ 它有继承SpringBootCondition,然后实现ConfigurationCondition


1、ConfigurationCondition

public interface ConfigurationCondition extends Condition
ConfigurationPhase getConfigurationPhase();
enum ConfigurationPhase
PARSE_CONFIGURATION,
REGISTER_BEAN


​ 这个接口主要是为了颗粒化控制Condition的作用&#xff0c;要对应匹配才会返回truePARSE_CONFIGURATION表面这个是Configuration即配置类&#xff0c;REGISTER_BEAN表示这个是一个常规Bean


2、SpringBootCondition

public abstract class SpringBootCondition implements Condition

​ SpringBootCondition是SpringBoot关于Condition的基础接口&#xff0c;也就是说&#xff0c;SpringBoot所有拓展Condition的都是通过SpringBootCondition来实现Condition接口。下面我们就来具体看下其的方法。


1)、matches

&#64;Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
String classOrMethodName &#61; getClassOrMethodName(metadata);
try
ConditionOutcome outcome &#61; getMatchOutcome(context, metadata);
logOutcome(classOrMethodName, outcome);
recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();

catch (NoClassDefFoundError ex)
throw new IllegalStateException("Could not evaluate condition on " ....
&#43; "put a &#64;ComponentScan in the default package by mistake)", ex);

catch (RuntimeException ex)
throw new IllegalStateException("Error processing condition on " &#43; getName(metadata), ex);


​ 这里首先是通过getMatchOutcome方法来获取匹配结果&#xff0c;然后再通过recordEvaluation来记录评估提交的详细结果&#xff0c;最后返回是否匹配。这里主要是getMatchOutcome方法&#xff0c;因为整个逻辑判断是否匹配都是这个方法&#xff0c;其他方法主要是记录一些信息&#xff0c;我们就略过了。

public class ConditionOutcome
private final boolean match;
private final ConditionMessage message;

2&#xff09;、getMatchOutcome

public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);

​ 这个方法是抽象方法&#xff0c;要让子类来处理是否匹配。

​ 下面我们就来具体看下这个SpringBootCondition的一个子类OnBeanCondition的实现逻辑。


3、getMatchOutcome(SpringBootCondition)

&#64;Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata)
ConditionMessage matchMessage &#61; ConditionMessage.empty();
if (metadata.isAnnotated(ConditionalOnBean.class.getName()))
..........

if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName()))
.........

if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName()))
........

return ConditionOutcome.match(matchMessage);

​ 这个我们可以看到其是会判断三种不同的注解ConditionalOnBeanConditionalOnSingleCandidateConditionalOnMissingBean。其中ConditionalOnSingleCandidate是用来判断是不是传入的类是不是有单例BeanConditionalOnMissingBean是判断有没有缺失这个Bean

&#64;Target( ElementType.TYPE, ElementType.METHOD )
&#64;Retention(RetentionPolicy.RUNTIME)
&#64;Documented
&#64;Conditional(OnBeanCondition.class)
public &#64;interface ConditionalOnMissingBean

&#64;Target( ElementType.TYPE, ElementType.METHOD )
&#64;Retention(RetentionPolicy.RUNTIME)
&#64;Documented
&#64;Conditional(OnBeanCondition.class)
public &#64;interface ConditionalOnSingleCandidate

​ 这里面的具体判断我们就先不深入探究了。


四、在注入的时候使用Conditional判断

​ 下面我们就来看下其是怎样使用的&#xff0c;我们在前面的两篇文章是有初步提到过得。


1、以PARSE_CONFIGURATION的方式(ConditionEvaluator源码调用)

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION))
return;

..........
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass &#61; asSourceClass(configClass);
do
sourceClass &#61; doProcessConfigurationClass(configClass, sourceClass);

while (sourceClass !&#61; null);
this.configurationClasses.put(configClass, configClass);

​ 这里就是在配置类的解析的前面以PARSE_CONFIGURATION也就是解析配置类的方式。

public boolean shouldSkip(&#64;Nullable AnnotatedTypeMetadata metadata, &#64;Nullable ConfigurationPhase phase)
if (metadata &#61;&#61; null || !metadata.isAnnotated(Conditional.class.getName()))
return false;

if (phase &#61;&#61; null)
.......

List<Condition> conditions &#61; new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata))
.........

AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions)
......

return false;

​ 下面我们就以前面的demo1为例&#xff0c;来看下这个过程。


1&#xff09;

if (metadata &#61;&#61; null || !metadata.isAnnotated(Conditional.class.getName()))
return false;

​ 首先是判断这个配置类有没有&#64;Conditional注解&#xff0c;如果没有就返回false&#xff0c;主要在processConfigurationClass就不会直接返回&#xff0c;是继续往下解析。


2&#xff09;

if (phase &#61;&#61; null)
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata))
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);

return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);

​ 下一步是类型phase判断&#xff0c;如果为null&#xff0c;就需要设置参数&#xff0c;这里的ConfigurationClassUtils.isConfigurationCandidate我们前面文章讲过&#xff0c;就是判断有没有&#64;Component&#64;Bean这些

static
candidateIndicators.add(Component.class.getName());
candidateIndicators.add(ComponentScan.class.getName());
candidateIndicators.add(Import.class.getName());
candidateIndicators.add(ImportResource.class.getName());

public static boolean isConfigurationCandidate(AnnotationMetadata metadata)
....
// Any of the typical annotations found?
for (String indicator : candidateIndicators)
if (metadata.isAnnotated(indicator))
return true;


// Finally, let&#39;s look for &#64;Bean methods...
try
return metadata.hasAnnotatedMethods(Bean.class.getName());

return false;


​ 是的话&#xff0c;就返回true表面其是PARSE_CONFIGURATION,不然的话就是一个普通的REGISTER_BEAN然后再重新进入shouldSkip方法&#xff0c;就能继续往下走了。


3&#xff09;

List<Condition> conditions &#61; new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata))
for (String conditionClass : conditionClasses)
Condition condition &#61; getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);


​ 这里就是获取&#64;Conditional的值&#xff0c;也就是实现Condition接口的类获取&#xff0c;然后通过getCondition去实例化创建添加到conditions中。例如我们的demo中&#xff0c;这里获取的就是NoBeanOneCondition


4&#xff09;

for (Condition condition : conditions)
ConfigurationPhase requiredPhase &#61; null;
if (condition instanceof ConfigurationCondition)
requiredPhase &#61; ((ConfigurationCondition) condition).getConfigurationPhase();

if ((requiredPhase &#61;&#61; null || requiredPhase &#61;&#61; phase) && !condition.matches(this.context, metadata))
return true;


return false;

​ 这里就是最后一步了&#xff0c;如果是ConfigurationCondition类型&#xff0c;由于其本身是有getConfigurationPhase()返回ConfigurationPhase的&#xff0c;所以这里会判断&#xff0c;然后就上课关键的condition.matches,就是调用Condition的方法&#xff0c;如果不匹配就会true&#xff0c;也就是注解return&#xff0c;不就行后续解析了。不然就最后返回false来继续解析。


五、在&#64;ComponentScan的使用。

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass var cpro_id = "u6885494";

推荐阅读
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • 使用圣杯布局模式实现网站首页的内容布局
    本文介绍了使用圣杯布局模式实现网站首页的内容布局的方法,包括HTML部分代码和实例。同时还提供了公司新闻、最新产品、关于我们、联系我们等页面的布局示例。商品展示区包括了车里子和农家生态土鸡蛋等产品的价格信息。 ... [详细]
  • Spring框架《一》简介
    Spring框架《一》1.Spring概述1.1简介1.2Spring模板二、IOC容器和Bean1.IOC和DI简介2.三种通过类型获取bean3.给bean的属性赋值3.1依赖 ... [详细]
  • 如何基于ggplot2构建相关系数矩阵热图以及一个友情故事
    本文介绍了如何在rstudio中安装ggplot2,并使用ggplot2构建相关系数矩阵热图。同时,通过一个友情故事,讲述了真爱难觅的故事背后的数据量化和皮尔逊相关系数的概念。故事中的小伙伴们在本科时参加各种考试,其中有些沉迷网络游戏,有些热爱体育,通过他们的故事,展示了不同兴趣和特长对学习和成绩的影响。 ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • 本文介绍了如何清除Eclipse中SVN用户的设置。首先需要查看使用的SVN接口,然后根据接口类型找到相应的目录并删除相关文件。最后使用SVN更新或提交来应用更改。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • 背景应用安全领域,各类攻击长久以来都危害着互联网上的应用,在web应用安全风险中,各类注入、跨站等攻击仍然占据着较前的位置。WAF(Web应用防火墙)正是为防御和阻断这类攻击而存在 ... [详细]
  • 本文讨论了在ASP中创建RazorFunctions.cshtml文件时出现的问题,即ASP.global_asax不存在于命名空间ASP中。文章提供了解决该问题的代码示例,并详细解释了代码中涉及的关键概念,如HttpContext、Request和RouteData等。通过阅读本文,读者可以了解如何解决该问题并理解相关的ASP概念。 ... [详细]
  • php缓存ri,浅析ThinkPHP缓存之快速缓存(F方法)和动态缓存(S方法)(日常整理)
    thinkPHP的F方法只能用于缓存简单数据类型,不支持有效期和缓存对象。S()缓存方法支持有效期,又称动态缓存方法。本文是小编日常整理有关thinkp ... [详细]
  • 2019独角兽企业重金招聘Python工程师标准
    本文介绍了2019独角兽企业对Python工程师的招聘标准,包括在AndroidManifest中定义meta-data的方法和获取meta-data值的代码。同时提供了获取meta-data值的具体实现方法。转载文章链接:https://my.oschina.net/u/244918/blog/685127 ... [详细]
  • xhci规范中整体软件模型如下图所示:这里描述的主要是上图中xhci部分,包括软件和硬件。1.接口架构这里主要分为三部分:1)HostConfigurationSpace.每个xH ... [详细]
  • 正则表达式及其范例
    为什么80%的码农都做不了架构师?一、前言部分控制台输入的字符串,编译成java字符串之后才送进内存,比如控制台打\, ... [详细]
  • Java编程思想一书中第21章并发中关于线程间协作的一节中有个关于汽车打蜡与抛光的小例子(原书的704页)。这个例子主要展示的是两个线程如何通过wait ... [详细]
author-avatar
平凡黯淡_551
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有